package net.towee.server.factories;

import javax.inject.Inject;

import net.towee.model.account.AccountIdentity;
import net.towee.model.discussion.DiscussionFactory;
import net.towee.model.repo.DocumentData;
import net.towee.model.repo.DocumentDiff;
import net.towee.model.repo.DocumentFactory;
import net.towee.model.repo.DocumentIdentity;
import net.towee.model.repo.DocumentNode;
import net.towee.model.repo.DocumentPatch;
import net.towee.model.repo.NodeData;
import net.towee.model.repo.TreeNode;
import net.towee.model.repo.impl.DocumentDataImpl;
import net.towee.model.repo.impl.DocumentDiffImpl;
import net.towee.model.repo.impl.DocumentIdentityImpl;
import net.towee.model.repo.impl.DocumentNodeImpl;
import net.towee.model.repo.impl.DocumentPatchImpl;
import net.towee.model.repo.impl.NodeDataImpl;
import net.towee.server.persistence.AccountStore;
import net.towee.server.persistence.DocumentStore;
import net.towee.server.persistence.GroupStore;
import net.towee.shared.exceptions.NameExistsException;
import net.towee.shared.exceptions.PersistenceException;

import org.bson.types.ObjectId;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.base.Preconditions;
import com.mycila.event.internal.guava.base.Strings;

/**
 * Implementation of document factory. It validate given parameters and create
 * document data and document node.
 * 
 * @author c58
 */
public class DocumentFactoryImpl implements DocumentFactory {
	private static final Logger LOG = LoggerFactory.getLogger(DocumentFactoryImpl.class);

	private final DocumentStore documentStore;
	private final GroupStore groupStore;
	private final AccountStore accountStore;
	private final DiscussionFactory discussionFactory;

	@Inject
	public DocumentFactoryImpl(DocumentStore store, AccountStore accStore, GroupStore groupStore,
			DiscussionFactory discFactory) {
		this.documentStore = store;
		this.accountStore = accStore;
		this.groupStore = groupStore;
		this.discussionFactory = discFactory;
	}

	@Override
	public DocumentData createEmptyDocument(String name, String groupId, String authorId,
			String parentNodeId) {
		Preconditions.checkArgument(!Strings.isNullOrEmpty(name), "null document name");
		Preconditions.checkArgument(!Strings.isNullOrEmpty(groupId), "null groupId");
		Preconditions.checkArgument(!Strings.isNullOrEmpty(authorId), "null authorId");

		try {
			// Create document identity
			DocumentIdentity identity = new DocumentIdentityImpl(new ObjectId().toString(), name,
					accountStore.getAccountIdentity(authorId), groupStore.getGroupIdentity(groupId));

			// Create document node
			DocumentNode node = new DocumentNodeImpl(identity.getId(), groupId, identity);

			if (parentNodeId != null) {
				NodeData parentNode = documentStore.getNode(parentNodeId);
				node.setParentId(parentNodeId);
				node.setPath(parentNode.getPathForChildren());
			}

			// Create empty document data
			DocumentData doc = new DocumentDataImpl(identity, node);
			doc.setDiscussion(discussionFactory.createDiscussion());
			doc.setDescription("");

			return doc;
		} catch (PersistenceException e) {
			LOG.error("Some data can't be found: authorId: " + authorId + "; groupId:" + groupId
					+ "; parentNode:" + parentNodeId);
		}

		return null;
	}

	@Override
	public DocumentData createDocument(DocumentData doc, String parentNodeId) throws NameExistsException {
		// Validate document data
		Preconditions.checkNotNull(doc, "null document");
		Preconditions.checkNotNull(doc.getIdentity(), "null document identity");
		Preconditions.checkNotNull(doc.getIdentity().getAuthor(), "null document author");
		Preconditions.checkNotNull(doc.getIdentity().getGroup(), "null document group");
		Preconditions.checkArgument(!Strings.isNullOrEmpty(doc.getIdentity().getName()),
				"null document name");

		// Generate id for document
		doc.setId(new ObjectId().toString());

		try {
			// Check existing node
			if( documentStore.checkNodeWithNameExisting(doc.getIdentity()
					.getGroup().getId(), parentNodeId, doc.getIdentity().getName()) )
				throw new NameExistsException();
			
			// Check given account and group identities
			accountStore.getAccountById(doc.getIdentity().getAuthor().getId(), "_id");
			groupStore.getGroupById(doc.getIdentity().getGroup().getId(), "_id");

			// Create document node
			DocumentNode node = new DocumentNodeImpl(doc.getIdentity().getId(), doc.getIdentity()
					.getGroup().getId(), doc.getIdentity());
			doc.setNode(node);

			if (parentNodeId != null) {
				NodeData parentNode = documentStore.getNode(parentNodeId);
				node.setParentId(parentNodeId);
				node.setPath(parentNode.getPathForChildren());
			}

			// Create discussion
			doc.setDiscussion(discussionFactory.createDiscussion());
			return doc;
		} catch (PersistenceException e) {
			LOG.error("Some data can't be found: authorId: " + doc.getIdentity().getAuthor().getId()
					+ "; groupId:" + doc.getIdentity().getGroup().getId() + "; parentNode:"
					+ parentNodeId);
		}
		
		return null;
	}

	@Override
	public TreeNode createTreeNode(String name, String parentNodeId, String groupId) throws NameExistsException {
		Preconditions.checkArgument(!Strings.isNullOrEmpty(name), "null document name");
		Preconditions.checkArgument(!Strings.isNullOrEmpty(groupId), "null group Id");

		try {
			// Check existing node
			if( documentStore.checkNodeWithNameExisting(groupId, parentNodeId, name) )
				throw new NameExistsException();
			
			// Get parent node
			TreeNode node = new NodeDataImpl(new ObjectId().toString(), groupId, name);

			if (parentNodeId != null) {
				NodeData parentNode = documentStore.getNode(parentNodeId);
				node.setParentId(parentNodeId);
				node.setPath(parentNode.getPathForChildren());
			}

			return node;
		} catch (PersistenceException e) {
			LOG.error("Error while adding tree node. Parent node with id " + parentNodeId
					+ " not exists!");
		}

		return null;
	}

	@Override
	public DocumentData createChangeset(String docId, DocumentData newDoc, String authorId, String explain) throws NameExistsException {
		Preconditions.checkArgument(!Strings.isNullOrEmpty(docId), "null doc id");
		Preconditions.checkArgument(!Strings.isNullOrEmpty(authorId), "null author id");
		Preconditions.checkArgument(!Strings.isNullOrEmpty(explain), "null explain");
		Preconditions.checkNotNull(newDoc.getNode(), "null document tree node");

		try {
			// Check existing node
			if(!documentStore.getNode(docId).getName().equals(newDoc.getIdentity().getName()) &&
					documentStore.checkNodeWithNameExisting(newDoc.getIdentity()
					.getGroup().getId(), newDoc.getNode().getParentId(), newDoc.getIdentity().getName()) )
				throw new NameExistsException();
			
			// Get author identity
			AccountIdentity author = accountStore.getAccountIdentity(authorId);

			// Create patch
			DocumentPatch patch = new DocumentPatchImpl(new ObjectId().toString(), newDoc,
					documentStore.getDocument(docId));

			// Create diff
			DocumentDiff diff = new DocumentDiffImpl(patch.getId(), author, explain);
			newDoc.getDiffs().add(diff);

			// Save patch
			documentStore.createPatch(patch);
			
			// Change node name
			newDoc.getNode().setName( newDoc.getIdentity().getName() );

			return newDoc;
		} catch (PersistenceException e) {
			LOG.error("Error while creating change: docId:" + docId + "; authorId:" + authorId);
		}

		return null;
	}

}
